<?php
/**
 * The model class file of ZenTaoPHP.
 *
 * ZenTaoPHP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * ZenTaoPHP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with ZenTaoPHP.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @copyright   Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com)
 * @author      Chunsheng Wang <chunsheng@cnezsoft.com>
 * @package     ZenTaoPHP
 * @version     $Id: model.class.php 117 2010-06-17 08:58:58Z wwccss $
 * @link        http://www.zentaoms.com
 */

/**
 * 模型基类。
 * 
 * @package ZenTaoPHP
 */
class model {
    /**
     * 全局的$app对象。
     *
     * @var object
     * @access protected
     */
    protected $app;

     /**
     * 全局的$dbh（数据库访问句柄）对象。
     *
     * @var object
     * @access protected
     */
    static $_dbhs;

	/**
	 * 当前数据操作对象
	 */
	protected $dbh;
    
    /**
     * 默认表名
     * @var <type>
     */
    protected $name;

    /**
     * 数据库配置名，默认db
     * @var <type>
     */
    protected $dbname;
    
    // 主键名称
    protected $pk  = 'id';
    
    // 查询表达式参数
    protected $options		= array();
    protected $_validate    = array();  // 自动验证定义
    protected $_auto        = array();  // 自动完成定义
    protected $_map         = array();  // 字段映射定义
    // 是否自动检测数据表字段信息
    protected $autoCheckFields   =   true;

    /**
     * 构造函数：
     *
     * 1. 引用全局变量，使之可以通过成员属性访问。
     * 2. 设置当前模块的路径、配置、语言等信息，并加载相应的文件。
     *
     * @access public
     * @return void
     */
    public function __construct($table = null, $dbname='db') {
        global $app;
        $this->app = $app;

        $moduleName = $this->getModuleName();
        $this->name = $table ? $table : $moduleName;

        $this->dbname = $dbname;

        // 字段检测
//        if(!empty($this->name) && $this->autoCheckFields)    $this->_checkTableInfo();
   }

    /**
     * 自动检测数据表信息
     */
    protected function _checkTableInfo() {
        // 如果不是Model类 自动记录数据表信息
        // 只在第一次执行记录
        if(empty($this->fields)) {
			// 每次都会读取数据表信息
			$this->fields = helper::F('_fields/'.$this->name);
            if(!$this->fields) $this->flush();
        }
    }

    /**
     * 获取字段信息并缓存
     */
    public function flush() {
        // 缓存不存在则查询数据表信息
		$this->fields = array();

		$dbh = self::getDbh($this->dbname);
        $stmt = $dbh->query("select * from " . $this->name . " where 1=2");
		for ($i = 0; $i < $stmt->columnCount(); $i ++) {
			$val = $stmt->getColumnMeta($i);
			$key = $val['name'];
			$this->fields[$key] = $val;
			
            // 记录主键
			if($val['flags'][1] == 'primary_key')
                $this->pk = $key;
		}
        // 永久缓存数据表信息
       	helper::F('_fields/'.$this->name, $this->fields);
    }
    
    /**
     * 获取主键名称
     */
    public function getPk() {
    	return $this->pk;
    }
    	
    /**
     * 连接到数据库，返回$dbh对象。
     *
     * @access public
     * @return object
     */
    static function getDbh($dbname)
    {
		if (self::$_dbhs [$dbname] instanceof PDO)
			return self::$_dbhs [$dbname];
		
		global $config,$app;
		$dbconfig = $config->$dbname;
		if (! isset ( $dbconfig->driver ))
			$app->error ( 'no pdo driver defined, it should be mysql or sqlite', __FILE__, __LINE__, $exit = true );
		if ($dbconfig->driver == 'mysql') {
			$dsn = "mysql:host={$dbconfig->host}; port={$dbconfig->port}; dbname={$dbconfig->name}";
			$dbh = new PDO ( $dsn, $dbconfig->user, $dbconfig->password, array (PDO::ATTR_PERSISTENT => $dbconfig->persistant ) );
		} elseif ($dbconfig->driver == 'mssql') {
			//根据不同操作系统加载不同的PDOC驱动
			if(strtolower(substr(PHP_OS,0,3))=='win') {
				$dsn = "sqlsrv:server={$dbconfig->host},{$dbconfig->port}; Database={$dbconfig->name}";
				$dbh = new PDO ( $dsn, $dbconfig->user, $dbconfig->password, array (PDO::ATTR_PERSISTENT => $dbconfig->persistant ) );
			}
			else 
			{
				$dsn = "dblib:host={$dbconfig->host}:{$dbconfig->port}; Database={$dbconfig->name}";
				$dbh = new PDO ( $dsn, $dbconfig->user, $dbconfig->password);
			}
		}
		try {			
			$dbh->setAttribute ( PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ );
			$dbh->setAttribute ( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
			//2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll()
			$dbh->setAttribute (PDO::ATTR_EMULATE_PREPARES, true);
			$dbh->setAttribute ( PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true );
			//mssql not exec
			if ($dbconfig->driver == 'mysql') 
				$dbh->exec ( "SET NAMES {$dbconfig->encoding}" );
			if (isset ( $dbconfig->strictMode ) and $dbconfig->strictMode == false)
				$dbh->exec ( "SET @@sql_mode= ''" );
			self::$_dbhs [$dbname] = $dbh;
			return $dbh;
		} catch ( PDOException $exception ) {
			$app->error ( $exception->getMessage (), __FILE__, __LINE__, $exit = true );
		}
    }

    /**
     * 对PDO的query进行封装
     * @param <type> $sql
     * @return <type>
     */
    public function query($statement) {
    	//$str = file_get_contents('a.txt');
    	//$fp = file_put_contents('a.txt',$str."\r\n".date('m-d H:i:s')."\r\n".json_encode($statement));
        $this->dbh = self::getDbh($this->dbname);
		$dbh = $this->dbh;
        if (is_array($statement)) {
            $stmt = $dbh->prepare($statement[0]);
            $stmt->execute($statement[1]);
        } else
            $stmt = $dbh->query($statement);
        $this->app->querynum ++;
        return $stmt;
    }

    /**
     * 对PDO的exec进行封装
     */
    public function exec($statement) {
        $this->dbh = self::getDbh($this->dbname);
		$dbh = $this->dbh;
        if (is_array($statement)) {
            $stmt = $dbh->prepare($statement[0]);
            $stmt->execute($statement[1]);
        } else
            $stmt = $dbh->exec($statement);
        $this->app->querynum ++;
        return $stmt;
    }

	/**
	 * 返回最新插入到数据库的行的ID值
	 */
	public function lastInsertId() {
		return $this->dbh->lastInsertId(); 
	}

    /**
     * 设置模块名：将类名中的model替换掉即为模块名。
     * 没有使用$app->getModule()方法，因为它返回的是当前调用的模块。
     * 而在一次请求中，当前模块的control文件很有可能会调用其他模块的model。
     *
     * @access protected
     * @return void
     */
    protected function getModuleName() {
        $parentClass = get_parent_class($this);
        $selfClass = get_class($this);
        $className = $parentClass == 'model' ? $selfClass : $parentClass;
        return strtolower(str_ireplace(array('ext', 'Model'), '', $className));
    }

    // insert into table_name (f1,f2[,..]) values(v1,v2[,..])
    const tpl_sql_insert = 'insert into %s (%s) values(%s)';
    // insert into table_name (f1,f2[,..]) values(v1,v2[,..])
    const tpl_sql_replace = 'replace into %s (%s) values(%s)';
    // select f1,f2[,..] from table_name [sql_conditions][sql_sort] [sql_limit]
    const tpl_sql_select = 'select %s from %s %s %s %s';
    // count count(*) from table_name [sql_conditions]
    const tpl_sql_count = 'select count(*) from %s %s';
    // update table_name set pair1,pair2 [sql_conditions]
    const tpl_sql_update = 'update %s set %s %s';
    // delete from table_names [sql_conditions] [sql_limit]
    const tpl_sql_delete = 'delete from %s %s %s';

    /**
     * 查询多条记录，优先使用预处理
     * @param <string|array> $conditions 默认空，例:array('id' => 1, 'name' => array('%Bob%','like'))
     * @param <string> $sort 默认空，例:id, time desc
     * @param <array|int> $limit 默认空，例:array(1, 5) 或 5
     * @param <string> $fields 默认*
     * @param <string> $table 表名，空才使用创建model对象时设置的默认表名
     * @return <type>
     */
    protected function findBy($conditions = null, $sort = null, $limit = null, $fields = '*', $table=null) {
    	$sql = $this->sql_select($conditions, $sort, $limit, $fields, $table);
        $stmt = $this->query($sql);
        return $stmt->fetchAll();
    }

    protected function count($conditions = null, $table=null) {
        $table_name = $table ? $table : $this->name;
        $sql_conditions = empty($conditions) ? '' : $this->sql_conditions($conditions);
        if (is_array($sql_conditions))
            $sql = array(sprintf(self::tpl_sql_count, $table_name, $sql_conditions[0]),
                    $sql_conditions[1]);
        else
            $sql = sprintf(self::tpl_sql_count, $table_name, $sql_conditions);
		$stmt = $this->query($sql);
		$count =  $stmt->fetchColumn();
        return $count;
    }

    /**
     * 经过封装的分页查询，可以设置自动取总数
     * @param <Page> $page 封装的Page对象
     * @param <string|array> $conditions 默认空，例:array('id' => 1, 'name' => array('%Bob%','like'))
     * @param <string> $fields 默认*
     * @param <string> $table 表名，空才使用创建model对象时设置的默认表名
     * @return <type>
     */
    protected function findPage($page, $conditions = null, $fields = '*', $table=null) {
        // 自动取总数
        if ($page->autoCount) {
            $totalCount = $this->count($conditions, $table);
			$page->totalCount = $totalCount;
		}
        if ($page->totalCount)
            $page->setResult($this->findBy($conditions, $page->orderBy, array($page->getFirst() - 1, $page->pageSize), $fields, $table));
        return $page;
    }

    /**
     * 获取单条记录，优先使用预处理
     * @param <string|array> $conditions 默认空，例:array('id' => 1, 'name' => array('%Bob%','like'))
     * @param <string> $sort 默认空，例:id, time desc
     * @param <string> $fields 默认*
     * @param <string> $table 表名，空才使用创建model对象时设置的默认表名
     * @return <type>
     */
    protected function findUnique($conditions = null, $sort = null, $fields = '*', $table=null) {
        $sql = $this->sql_select($conditions, $sort, 1, $fields, $table);
        $stmt = $this->query($sql);
        return $stmt->fetch();
    }

    /**
     * 更新记录，优先使用预处理
     * @param <array> $record 更新的数据数组
     * @param <String|array> $conditions 默认空，例:array('id' => 1, 'name' => array('%Bob%','like'))
     * @param <string> $table 表名
     * @return <type>
     */
    protected function update($record, $conditions=null, $table=null) {
        $table_name = $table ? $table : $this->name;
		
        if (!empty($record) && is_array($record)) {
        	// 没有条件，取record中的主键
        	if (empty($conditions) && !empty($record[$this->pk]))
        		$conditions = array($this->pk => $record[$this->pk]);
        	if (empty($conditions))
				$this->app->error ( '条件和数据的主键不能同时为空', __FILE__, __LINE__, $exit = true );
        		
			$sql_conditions =  $this->sql_conditions($conditions);
            $pairs = array();
            foreach ($record as $field => $val) {
                $pairs[] = "`{$field}`=?";
                $values[] = $val;
            }
            $pairs = implode(',', $pairs);
            if (is_array($sql_conditions))
                $sql = array(sprintf(self::tpl_sql_update, $table_name, $pairs, $sql_conditions[0]),
                        array_merge($values, $sql_conditions[1]));
            else
                $sql = array(sprintf(self::tpl_sql_update, $table_name, $pairs, $sql_conditions),
                        $values);
             
            $str = file_get_contents('a.txt');
    	$fp = file_put_contents('a.txt',$str."\r\n".date('m-d H:i:s')."\r\n".json_encode($sql));
            $row = $this->exec($sql);
            return $row->rowCount();
        }
        return 0;
    }

    /**
     * 插入记录，优先使用预处理
     * @param <array> $record 插入的数据数组
     * @param <string> $table 表名
     * @return <type>
     */
    protected function insert($record, $table=null,$replace=false) {
        $table_name = $table ? $table : $this->name;
        $sql_method = $replace ? self::tpl_sql_replace : self::tpl_sql_insert;
        if (!empty($record) && is_array($record)) {
            $columns = '`'.implode('`,`', array_keys($record)).'`';
            $values = array_fill(0, count($record), "?");
            $values = implode(',', $values);
            $sql = array(sprintf($sql_method, $table_name, $columns, $values),
                    array_values($record));
                    
            $this->exec($sql);
            return $this->dbh->lastInsertId();
        }
        return 0;
    }

    /**
     * 删除记录，优先使用预处理
     * @param <string|array> $conditions 默认空，例:array('id' => 1, 'name' => array('%Bob%','like'))
     * @param <array|int> $limit 默认空，例:array(1, 5) 或 5
     * @param <string> $table 表名，空才使用创建model对象时设置的默认表名
     * @return <type>
     */
    protected function delete($conditions, $limit=null, $table=null) {
        if (empty($conditions))
			$this->app->error ( '删除操作，条件不能为空', __FILE__, __LINE__, $exit = true );
    	
		$table_name = $table ? $table : $this->name;

        $sql_conditions = empty($conditions) ? '' : $this->sql_conditions($conditions);
        if (is_array($sql_conditions))
            $sql = array(sprintf(self::tpl_sql_delete ,
                $table_name,$sql_conditions[0],$this->limit_string($limit)), $sql_conditions[1]);
        else
            $sql = sprintf(self::tpl_sql_delete ,
                $table_name,$sql_conditions,$this->limit_string($limit));
        return $this->exec($sql);
    }

    private function sql_select($conditions = null, $sort = null, $limit = null, $fields = '*', $table=null) {
        $table_name = $table ? $table : $this->name;
        // 排序
        $sql_sort = $sort ? " order by {$sort}" : '';
        // 范围
        $sql_limit = $this->limit_string($limit);
        // 条件
        $sql_conditions = empty($conditions) ? '' : $this->sql_conditions($conditions);

        if (is_array($sql_conditions))
            return array(sprintf(self::tpl_sql_select,
                    $fields, $table_name, $sql_conditions[0], $sql_sort, $sql_limit),
                $sql_conditions[1]);
        else
            return sprintf(self::tpl_sql_select,
                    $fields, $table_name, $sql_conditions, $sql_sort, $sql_limit);
    }
    

    private function limit_string($limit) {
        if (is_array($limit)) {
            list($offset, $length) = $limit;
        } else {
            $length = $limit;
            $offset = null;
        }
        return empty($length) ? '' : $this->sql_limit($length, $offset);
    }

    private function sql_limit($length, $offset = null) {
        $sql = '';
        if (!empty($offset)) {
            $sql = sprintf(' limit %d, %d',
                            (int) $offset, empty($length) ? 50000 : (int) $length);
        } else if (!empty($length)) {
            $sql = " limit " . (int) $length;
        }
        return $sql;
    }

    private function sql_conditions($conditions = null) {
        if (empty($conditions))
            return '';
        $sql = ' WHERE ';
        if (is_string($conditions))
            return $sql . $conditions;
        else if (is_array($conditions)) {
            $join_char = ''; // 第一个条件前面 没有 and 连接符
            foreach ($conditions as $field => $cond ) {
				// 支持 like / or 等操作 例如: 'name' => array('%Bob%','like')
				$op_char = '=';
				if (is_array ( $cond )) {
					$value = array_shift ( $cond );
					// if $value is array , will use "in" [] opchar
					if (is_array ( $value ))
						$value = '[' . implode ( ',', $value ) . ']';
					
					$_op_char = array_shift ( $cond );
					if (! empty ( $_op_char ) && is_string ( $_op_char ))
						$op_char = $_op_char;
				} else {
					$value = $cond;
				}
				// 过滤值
				$values [] = $value;
				
				$sql .= "{$join_char} {$field} {$op_char} ? ";
                $join_char = ' and ';
            }
            return array($sql, $values);
        }
        return '';
    }
    
    
/**
	 * 经过封装的分页查询，可以设置自动取总数
	 * @param <Page> $page 封装的Page对象
	 * @param <string> $sql
	 */
	protected function findPageBySql($page, $sql, $conditions = null, $field = '*',$table=null) {
		$sql_conditions = empty ( $conditions ) ? '' : $this->getconditions ( $conditions );
		// 自动取总数
		if ($page->autoCount) {
			$sqlCount = $sql." %s " ;
			if (is_array($sql_conditions))
				$sqlCount = array (sprintf ( $sqlCount, "count(*)", $sql_conditions [0] ), $sql_conditions [1] );
       		else
           	 	$sqlCount = sprintf($sqlCount,"count(*)",$sql_conditions);
           	$stmt = $this->query ( $sqlCount );
			$totalCount = $stmt->fetchColumn ();
			$page->totalCount = $totalCount;
		}
		if ($page->totalCount) {
			$page->setResult($this->findBySql($sql,$conditions, $page->orderBy, array($page->getFirst() - 1, $page->pageSize), $field, $table));
		}
		return $page;
	}
	/**
	 * 经过封装的sql查询， 
	 * @param <string> $sql
	 */
	protected function findBySql( $sql, $conditions = null,$sort = null, $limit = null, $field = '*') {
		$sql.=" %s %s %s";
		$sql = $this->sql_select_bysql($sql,$conditions, $sort, $limit, $field);
		$stmt = $this->query ( $sql );
		$record = $stmt->fetchAll ();
		return $record;
	}

	private function getconditions($conditions = null) {
		if (empty ( $conditions ))
			return '';
		$sql = ' where  ';
		if (is_string ( $conditions ))
			return $sql . $conditions;
		else if (is_array ( $conditions )) {
			$join_char = ''; // 第一个条件前面 没有 and 连接符
			foreach ( $conditions as $field => $cond ) {
				// 支持 like / or 等操作 例如: 'name' => array('%Bob%','like')
				$op_char = '=';
				if (is_array ( $cond )) {
					$value = array_shift ( $cond );
					// if $value is array , will use "in" [] opchar
					if (is_array ( $value ))
						$value = '[' . implode ( ',', $value ) . ']';
					
					$_op_char = array_shift ( $cond );
					if (! empty ( $_op_char ) && is_string ( $_op_char ))
						$op_char = $_op_char;
				} else {
					$value = $cond;
				}
				// 过滤值
				$values [] = $value;
				
				$sql .= "{$join_char} {$field} {$op_char} ? ";
				$join_char = ' and ';
			}
			return array ($sql, $values );
		}
		return '';
	}
	
    private function sql_select_bysql($sql,$conditions = null, $sort = null, $limit = null, $fields = '*') {
        // 排序
        $sql_sort = $sort ? " order by {$sort}" : '';
        // 范围
        $sql_limit = $this->limit_string($limit);
        
        // 条件
        $sql_conditions = empty($conditions) ? '' : $this->getconditions($conditions);
		
        if (is_array($sql_conditions))
            return array(sprintf($sql,
                    $fields, $sql_conditions[0], $sql_sort, $sql_limit),
                $sql_conditions[1]);
        else
            return sprintf($sql,
                    $fields, $sql_conditions, $sql_sort, $sql_limit);
    }
    
	
}


class Page {
	public $pk = "id";

    //-- 分页参数 --//
    public $pageNo;
    public $pageSize;
    /** 排序，例:id, time desc */
	public $orderBy;
    public $autoCount;

    //-- 返回结果 --//
    public $result;
    public $totalCount = 0;

    /**
     * 构造函数
     * @param <type> $pageNo 当前页，>=1默认1
     * @param <type> $pageSize 页大小，>0默认25
     * @param <type> $autoCount 是否自动取总数
     */
    public function __construct($pageNo = 1, $pageSize = 25, $autoCount = true) {
        $this->pageNo = $pageNo < 1 ? 1 : (int) $pageNo;
        $this->pageSize = $pageSize > 0 ? (int) $pageSize : 25;
        $this->autoCount = $autoCount;
    }

	/**
	 * 根据pageNo和pageSize计算当前页第一条记录在总结果集中的位置,序号从1开始.
	 */
	public function getFirst() {
		return (($this->pageNo - 1) * $this->pageSize) + 1;
	}

    /**
     * 设置结果，做一些附加处理
     */
    public function setResult($result) {
        $this->result = $result;
    }
}
/**
 * 查询
 */
class ActionQuery {
	public $fields ;
	public $conditions;
	public $orderBys;
	
	/**
	 * 构造函数
	 * @param <type> $fields 需返回的字段列表 :id,title,nick,pic_url
	 * @param <type> $conditions 查询条件
	 * @param <type> $orderBys  
	 */
	public function __construct() {
		$conditions = array ();
		$orderBys = '';
//		$orderBys = array ();
	}
	
	/**
	 * 添加查询条件
	 */
	public function addCondition($field, $value, $operator = "=") {
		if ($operator == "=") {
			$this->conditions [$field] = $value;
		} else {
			$this->conditions [$field] = array ($value, $operator );
		}
	}
	
	/**
	 * 添加排序条件
	 */
	public function addOrderbys($field, $order) {
		$this->orderBys = $field.' '.$order;
//		$this->$orderBys = array ($field, $order );
	}
	public function toString(){
		$sql = ' ';
		$conditions  = $this->conditions ;
        if (is_string($conditions)){
            return $sql . $conditions;
        }
		elseif(is_array($conditions)){
			$join_char = ''; // 第一个条件前面 没有 and 连接符
			foreach ( $conditions as $field => $cond ) {
				// 支持 like / or 等操作 例如: 'name' => array('%Bob%','like')
				$op_char = '=';
				if (is_array ( $cond )) {
					$value = array_shift ( $cond );
					// if $value is array , will use "in" [] opchar
					if (is_array ( $value ))
						$value = '[' . implode ( ',', $value ) . ']';
					
					$_op_char = array_shift ( $cond );
					if (! empty ( $_op_char ) && is_string ( $_op_char ))
						$op_char = $_op_char;
					$value=  $op_char." '".$value."'";
				}elseif(strpos($cond,"null")>-1){
					if(strpos($cond,"is")<0){
						$value = " is ".$cond ;
					}else{
						$value = $cond ;
					}
				}elseif(strpos($cond,",")>-1||preg_match("/not |in /",$field)){
					if(preg_match("/in /",$field)){
						$value = " ('" . str_replace(',', "','", str_replace(' ', '',$cond)) . "')" ;
					}else{
						$value = "IN ('" . str_replace(',', "','", str_replace(' ', '',$cond)) . "')" ;
					}
					
				}elseif (preg_match("/>|<|<>/",$cond)) {
					$value = $cond;
				}
				 else {
					$value = ' = '.$cond;
				}
				$sql .= "{$join_char} {$field} {$value}  ";
				$join_char = ' and ';
			}
			return $sql;
		}
	}
}